#!/usr/bin/python
# -*- coding: utf-8 -*-

##
 # Copyright 2009 Caio Souza
 # This file is part of Rpyg.
 #
 # Rpyg is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # Rpyg is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with Rpyg. If not, see <http://www.gnu.org/licenses/>.
 ##

'''Módulo de definição de um personagem controlável'''

from threading import Thread

import pygame

from rpyg.Event import *
from rpyg.State import *
from rpyg.Controller import *
from rpyg.Screen import Screen

class Character (Thread):
    '''Classe de abstração de um personagem do jogo'''

    def __init__(self, images, controller, pos, offset = (0, 0), facing = KEY_SOUTH, cycles = 8, time = 40):
        '''
        Cria um novo personagem controlável
        
        Cria um personagem a partir do dicionário de imagens, com quatro imagens
        para cada ponto cardeal, um controle, uma posição no mapa de tiles, o
        deslocamento do centro do tile, a direção que o personagem está olhando,
        o número de ciclos necessários para o personagem deslocar em cada tile e
        o tempo para cada passo do deslocamento. 
        '''

        Thread.__init__(self)

        self._images = {}
        '''Dicionário de imagens do personagem.'''

        self._images[KEY_NORTH] = images['north']
        self._north = images['north']
        '''Imagem do player olhando para o norte.'''

        self._images[KEY_EAST] = images['east']
        self._east = images['east']
        '''Imagem do player olhando para o leste.'''

        self._images[KEY_WEST] = images['west']
        self._west = images['west']
        '''Imagem do player olhando para o oeste.'''

        self._images[KEY_SOUTH] = images['south']
        self._south = images['south']
        '''Imagem do player olhando para o sul.'''

        self._north.stop()
        self._east.stop()
        self._west.stop()
        self._south.stop()

        self._walking = False
        '''Booleano que indica se o personagem está em movimento.'''

        self._image = None
        '''Imagem do personagem.'''

        if facing == KEY_EAST: self._image = self._east
        elif facing == KEY_NORTH: self._image = self._north
        elif facing == KEY_WEST: self._image = self._west
        else: self._image = self._south

        self._controller = controller
        '''Controle do personagem.'''

        self._running = True
        '''Booleano de estado da thread.'''

        self._tile = tuple(pos)
        '''Tile que o personagem está ocupando.'''

        self._offset = offset
        '''Deslocamento com relação ao centro do tile.'''

        self._clock = pygame.time.Clock()
        '''Relógio usado para controle dos frames por segundo.'''

        self._facing = facing
        '''Direção que o personagem está olhando.'''

        self._cycles = cycles
        '''Número de cycles de deslocamento entre tiles.'''

        self._time = time
        '''Tempo de deslocamento para cada passo.'''

        self._tilemap = None
        '''Mapa de tiles onde o personagem se desloca.'''

        self._events = []
        '''Lista de eventos do personagem.'''

        self.start()

    def _nextTile(self, deltax, deltay):
        '''Retorna o tile que o personagem está tentando se mover.'''

        if not deltax: tilex = 0
        elif deltax > 0: tilex = 1
        else: tilex = -1

        if not deltay: tiley = 0
        elif deltay > 0: tiley = 1
        else: tiley = -1

        tilex = self._tile[0] + tilex
        tiley = self._tile[1] + tiley

        return tilex, tiley

    def _step(self, key, image, deltax, deltay):
        '''
        Desloca o personagem entre os tiles.
        
        Desloca a posição o personagem em 'deltax' e 'deltay' pixels
        distribuindo esse valor através do número de ciclos e pausando
        a execução pelo tempo determinado.
        '''

        tile = self._tile
        self._tile = self._nextTile(deltax, deltay)
        self._controller.moved()
        self._tilemap.setBlocked(self._tile[0], self._tile[1], self)

        image.resume ()

        if key != self._facing:
            self._image.stop()
            image.resume()

        self._facing = key
        self._image = image
        self._walking = True

        deltax /= self._cycles
        deltay /= self._cycles

        self._image = image
        for _ in range(self._cycles):
            self._pos[0] += deltax
            self._pos[1] += deltay

            self._controller.setMapPos(*self._pos)

            pygame.time.wait(self._time)

        self._tilemap.setUnblocked(tile[0], tile[1], self)

    def _moveKey(self, key):
        '''Configura a movimentação do personagem a partir da ação tomada.'''

        if key == KEY_NORTH: self._step(key, self._north, 0, -self._tilemap.deltay)
        elif key == KEY_EAST: self._step(key, self._east, self._tilemap.deltax, 0)
        elif key == KEY_WEST: self._step(key, self._west, -self._tilemap.deltax, 0)
        elif key == KEY_SOUTH: self._step(key, self._south, 0, self._tilemap.deltax)

    def run(self):
        '''
        Função de execução da thread, atualiza a posição do personagem com base
        na ação retornada pelo controle.
        '''

        while self._running:
            self._clock.tick(10)
            state = self._controller.get(*self._tile)

            if state.type == DO_NOTHING:
                if self._walking:
                    self._walking = False
                    self._image.stop()
                    
            elif state.type == KEY_ACT:
                act_i, act_j = incTile (self._tile[0], self._tile[1], self._facing)
                if self._facing == KEY_NORTH: self._tilemap.executeEvent(ACT_FROM_SOUTH, act_i, act_j)
                elif self._facing == KEY_SOUTH: self._tilemap.executeEvent(ACT_FROM_NORTH, act_i, act_j)
                elif self._facing == KEY_EAST: self._tilemap.executeEvent(ACT_FROM_WEST, act_i, act_j)
                elif self._facing == KEY_WEST: self._tilemap.executeEvent(ACT_FROM_EAST, act_i, act_j)

            elif state.type == MOVE_KEY: self._moveKey(state.key)

            elif state.type == LOOK_AT:
                self._image.stop()
                self._facing = state.key
                self._image = self._images[state.key]

    def update(self):
        '''Atualiza a tela com a imagem do personagem.'''

        self._tilemap.blit(self._image.getSurface(), self._pos)

    def exit(self):
        '''Termina a thread do personagem.'''

        self._running = False

    def setTileMap(self, tilemap):
        '''Diz ao personagem sobre qual mapa ele irá andar.'''

        self._pos = [self._tile[0] * tilemap.deltax + self._offset[0], self._tile[1] * tilemap.deltay + self._offset[1]]
        '''Posição em pixels do personagem no mapa.'''

        self._tilemap = tilemap
        self._controller.setTileMap(tilemap)
        self._controller.setMapPos(*self._pos)
        self._tilemap.setBlocked(self._tile[0], self._tile[1], self)

    def addEvent(self, event):
        '''Adiciona um evento a lista de eventos do personagem.'''

        self._events.append(event)
        
    def executeEvent(self, trigger):
        '''Executa um evento da lista de eventos.'''
        
        for event in self._events: event.execute(trigger)

    def __cmp__(self, other):
        '''Compara a posição de dois personagens.'''

        if self._pos[1] <= other._pos[1]: return - 1
        return 1
